SQL — Daten abfragen und schreiben
SQL (Structured Query Language) ist die Sprache für den Umgang mit relationalen Datenbanken. In 42°OS begegnet dir SQL an zwei Stellen: beim Zugriff auf externe Datenbanken über den Database Agent und beim Zugriff auf den plattformeigenen Internal Storage.
Wann SQL in 42°OS?
Viele Unternehmen betreiben ERP- oder CRM-Systeme ohne nutzbare API — oder die verfügbare API wurde nicht lizenziert. In diesen Fällen ist der direkte Datenbankzugriff per SQL oft der pragmatischste Weg, um Daten zu lesen oder zu schreiben.
Darüber hinaus nutzt du SQL im Internal Storage für alles, was persistiert werden muss: Zustandsdaten einer State Machine, Mapping-Tabellen, Zwischenergebnisse, Gedächtnis-Einträge.
Die vier Grundoperationen
SELECT — Daten lesen
SELECT spalte1, spalte2
FROM tabellenname
WHERE bedingung
ORDER BY spalte1 DESC
LIMIT 100
SELECT * liest alle Spalten — praktisch zum Erkunden, aber in Produktiv-Workflows möglichst durch explizite Spaltennamen ersetzen.
-- Alle aktiven Kunden, neueste zuerst
SELECT kundennummer, name, email
FROM kunden
WHERE status = 'aktiv'
ORDER BY erstellt_am DESC
LIMIT 50
INSERT — Daten einfügen
INSERT INTO tabellenname (spalte1, spalte2, spalte3)
VALUES ('wert1', 'wert2', 'wert3')
INSERT INTO verarbeitete_dokumente (dokument_id, typ, lieferant, erstellt_am)
VALUES ('{{ dokument_id }}', '{{ typ }}', '{{ lieferant }}', datetime('now'))
UPDATE — Daten aktualisieren
UPDATE tabellenname
SET spalte1 = 'neuer_wert', spalte2 = 'anderer_wert'
WHERE bedingung
UPDATE bestellungen
SET status = 'verarbeitet', aktualisiert_am = datetime('now')
WHERE bestell_id = '{{ bestell_id }}'
Ein UPDATE ohne WHERE-Bedingung aktualisiert alle Zeilen in der Tabelle. Im Zweifel lieber einmal zu viel prüfen.
DELETE — Daten löschen
DELETE FROM tabellenname
WHERE bedingung
Auch hier gilt: ohne WHERE-Bedingung werden alle Zeilen gelöscht.
Filtern und Vergleichen
-- Gleichheit
WHERE status = 'aktiv'
-- Ungleichheit
WHERE status != 'gelöscht'
-- Zahlenvergleiche
WHERE betrag > 1000
WHERE betrag BETWEEN 500 AND 2000
-- Text-Suche (Groß-/Kleinschreibung beachten)
WHERE name LIKE '%Muster%' -- enthält "Muster"
WHERE name LIKE 'Muster%' -- beginnt mit "Muster"
-- NULL-Prüfung
WHERE kundennummer IS NULL
WHERE kundennummer IS NOT NULL
-- Mehrere Werte
WHERE status IN ('neu', 'offen', 'in_bearbeitung')
-- Kombinieren
WHERE status = 'aktiv' AND betrag > 500
WHERE status = 'neu' OR status = 'offen'
Aggregieren und Gruppieren
SELECT
abteilung,
COUNT(*) AS anzahl_mitarbeiter,
AVG(gehalt) AS durchschnittsgehalt,
MAX(eintrittsdatum) AS letzter_eintritt
FROM mitarbeiter
WHERE status = 'aktiv'
GROUP BY abteilung
ORDER BY anzahl_mitarbeiter DESC
Häufige Aggregatfunktionen:
| Funktion | Bedeutung |
|---|---|
COUNT(*) | Anzahl Zeilen |
COUNT(spalte) | Anzahl nicht-NULL-Werte |
SUM(spalte) | Summe |
AVG(spalte) | Durchschnitt |
MIN(spalte) | Kleinster Wert |
MAX(spalte) | Größter Wert |
Tabellen verbinden: JOIN
SELECT
b.bestell_id,
b.datum,
k.name AS kundenname,
k.email
FROM bestellungen b
JOIN kunden k ON b.kunden_id = k.id
WHERE b.status = 'offen'
| JOIN-Typ | Bedeutung |
|---|---|
JOIN / INNER JOIN | Nur Zeilen die in beiden Tabellen vorkommen |
LEFT JOIN | Alle Zeilen links, rechts NULL wenn kein Treffer |
RIGHT JOIN | Alle Zeilen rechts, links NULL wenn kein Treffer |
SQL in 42°OS: die zwei Wege
Internal Storage Agent — plattformintern
Für den plattformeigenen SQLite-Speicher. Kein externer Datenbankserver nötig, keine Credentials. SQL-Statement direkt in der Agent-Konfiguration eingeben, Liquid Templating für dynamische Werte:
SELECT dokument_id, typ, lieferant
FROM verarbeitete_dokumente
WHERE dokument_id = '{{ dokument_id }}'
Detailliert beschrieben im Kapitel Zustandsnormalisierung durch SQL-Persistenz.
Database Agent — externe Datenbanken
Für den Zugriff auf PostgreSQL, MySQL und Microsoft SQL Server. Credentials werden im Credential-Store der Plattform hinterlegt — Passwörter erscheinen nie im Klartext in der Agent-Konfiguration:
{
"host": "db.unternehmen.intern",
"database": "erp_produktion",
"username": "42os_readonly",
"password": "..."
}
Der Credential-Eintrag wird im Agent nur per Name referenziert:
db_credentials: erp_produktion_credentials
db_type: postgres
Das SQL-Statement selbst funktioniert identisch — inkl. Liquid Templating. Die Ausgabe landet standardmäßig als JSON-Array unter dem Schlüssel result in der Nachricht.
Für Workflows die nur Daten lesen, empfiehlt sich ein dedizierter Datenbanknutzer mit reinen SELECT-Rechten. Das begrenzt den Schaden bei Konfigurationsfehlern und ist eine klare Sicherheitslinie.
Liquid Templating in SQL-Statements
Beide Agents unterstützen Liquid-Ausdrücke direkt im SQL:
-- Einfacher Wert
WHERE bestell_id = '{{ bestell_id }}'
-- Mit Standardwert falls Variable fehlt
LIMIT {{ limit | default: 100 }}
-- Datumsfilter
WHERE erstellt_am >= '{{ start_datum }}'
AND erstellt_am <= '{{ end_datum }}'
-- Bedingte WHERE-Klausel
{% if abteilung %}
WHERE abteilung = '{{ abteilung }}'
{% endif %}
Häufige Fehler
Fehlende Anführungszeichen bei Strings — Zahlen brauchen keine Anführungszeichen, Strings schon:
WHERE id = {{ id }} -- ✅ Zahl
WHERE name = '{{ name }}' -- ✅ String
WHERE name = {{ name }} -- ❌ führt zu SQL-Fehler
Groß-/Kleinschreibung bei LIKE — SQLite ist standardmäßig case-insensitive für ASCII, PostgreSQL nicht. Für konsistentes Verhalten LOWER() nutzen:
WHERE LOWER(name) LIKE LOWER('%{{ suchbegriff }}%')
Zeitformat — SQLite erwartet Datumsangaben als Text im Format YYYY-MM-DD oder YYYY-MM-DDTHH:MM:SS. PostgreSQL hat eigene Datums-Typen und benötigt ggf. Casting: '{{ datum }}'::timestamp.